//
//  NetworkTools.swift
//  MyWeibo
//
//  Created by Roman on 15/9/7.
//  Copyright © 2015年 Roman. All rights reserved.
//

import UIKit
import AFNetworking

/// 错误的类别标记
private let XJErrorDomainName = "com.roman.error.network"

/// 网络访问错误信息
/// Swift 中枚举可以定义函数和属性, 跟'类'有点像
private enum XJNetworkError: Int {
    case emptyDataError = -1
    case emptyTokenError = -2

    private var errorDescrption: String {
        switch self {
        case .emptyDataError: return "空数据"
        case .emptyTokenError: return "Token 为空"
        }
    }
    
    /// 根据枚举类型, 返回对应的错误
    private func error() -> NSError {
        return NSError(domain: XJErrorDomainName, code: rawValue, userInfo: [XJErrorDomainName : errorDescrption])
    }
}

/// 网络访问方法
private enum XJNetworkMethod: String {
    case GET = "GET"
    case POST = "POST"
}

class NetworkTools: AFHTTPSessionManager {
    
    // MARK: - 应用程序信息
    // AppKey
    private let clientId = "1841189095"
    // AppSecret
    private let appSecret = "0b3306696751da64ec67539849261cc5"
    // 回调地址
    let redirectUri = "http://www.baidu.com"
    
    // MARK: - 类型定义
    /// 网络回调类型别名
    typealias XJNetFinishedCallBack = (result: [String: AnyObject]?, error: NSError?)->()
    
    // 单例
    static let sharedTools: NetworkTools = {
        let baseURL = NSURL(string: "https://api.weibo.com/")!
        let tools = NetworkTools(baseURL: baseURL)
        
        // 设置数据解析数据类型
        tools.responseSerializer.acceptableContentTypes = NSSet(objects: "application/json", "text/json", "text/javascript", "text/plain") as Set<NSObject>
        
        return tools
    }()
    
    
    /// 检查并生成 token 字典
    private func tokenDict(finished: XJNetFinishedCallBack) -> [String : AnyObject]? {
        //  判断 token 是否存在
        if UserAccount.sharedAccount?.access_token == nil {
            // 错误回调, token为空
            let error = XJNetworkError.emptyTokenError.error()
            
            print(error)
            finished(result: nil, error: error)
            
            return nil
        }
        // 生成 token 字典返回
        return ["access_token": UserAccount.sharedAccount!.access_token!]
    }
    
    // MARK: - 发布微博
    ///  发布微博
    ///
    ///  - parameter status:   微博文字
    ///  - parameter image:    微博图片
    ///  - parameter finished: 完成回调
    func sendStatus(status: String, image: UIImage?, finished: XJNetFinishedCallBack) {
        guard var params = tokenDict(finished) else {
            return
        }
        
        params["status"] = status
        
        // POST 发布文本微博
        // 判断是否有图片, 如果没有, 发布文本微博
        if image == nil {
            request(XJNetworkMethod.POST, urlString: "2/statuses/update.json", params: params, finished: finished)
        } else {    // 发布图片微博
            // 在使用 AFN 的时候, 如果与 BaseURL 不一样, 可以使用全路径
            uploadImage("https://upload.api.weibo.com/2/statuses/upload.json", image: image!, params: params, finished: finished)
        }
    }
    
    // MARK: - 加载微博数据
    /**
        since_id    则返回ID比since_id大的微博(即比since_id时间晚的微博), 默认为0
        max_id      则返回ID小于或等于max_id的微博, 默认为0
    */
    func loadStatus(since_id: Int, max_id: Int, finished: XJNetFinishedCallBack) {
        
        // 判断 token 是否存在, 为 nil 直接返回
        // let 获得的变量, 后续可以直接使用, 而且一定不为 nil
        guard var params = tokenDict(finished) else {
            // params 在此为 nil, 一般直接返回
            // 提示: tokenDict已经做了token为空的错误回调, 这里不再做
            return
        }
        
        if since_id > 0 {
            params["since_id"] = since_id
        }
        if max_id > 0 {
            params["max_id"] = max_id - 1
        }
        
        // 代码运行至此, params 一定有值
        let urlString = "2/statuses/home_timeline.json"
        
        request(XJNetworkMethod.GET, urlString: urlString, params: params, finished: finished)
    }
    
    // MARK: - 加载用户数据
    ///  加载用户信息  -  职责: 做网络访问, 获取到 dict(字典)
    ///
    ///  :param: uid      用户代号字符串
    ///  :param: finished 完成回调
    func loadUserInfo(uid: String, finished: XJNetFinishedCallBack) {

        // guard 不仅可以用 let, 而且可以用 var
        guard var params = tokenDict(finished) else {
            // params 在此为 nil, 一般直接返回
            return
        }
        
        let urlString = "2/users/show.json"
        params["uid"] = uid
    
        // 发送网络请求
        // 如果参数不正确, 首先用 option + click 确认参数类型
        request(XJNetworkMethod.GET, urlString: urlString, params: params, finished: finished)
    }
    
    // MARK: - OAuth授权
    /// 返回 OAuth() 授权地址
    func oauthUrl() -> NSURL {
        let urlString = "https://api.weibo.com/oauth2/authorize?client_id=\(clientId)&redirect_uri=\(redirectUri)"
        
        return NSURL(string: urlString)!
    }
    
    /// 加载 Token
    func loadAccessToken(code: String, finished: XJNetFinishedCallBack) {
        let urlString = "https://api.weibo.com/oauth2/access_token"
        let params = ["client_id" : clientId,
                    "client_secret" : appSecret,
                    "grant_type" : "authorization_code",
                    "code" : code,
                    "redirect_uri" : redirectUri]

        request(XJNetworkMethod.POST, urlString: urlString, params: params, finished: finished)
        
    }
    
    
    // MARK: - 封装 AFN 网络方法, 便于替换网络访问方法, 第三方框架的网络代码全部集中于此
    ///  AFN 网络请求 GET / POST
    ///
    ///  :param: method    HTTP方法 GET / POST
    ///  :param: urlString URL字符串
    ///  :param: params    字典参数
    ///  :param: finished  完成回调
    private func request(method: XJNetworkMethod, urlString: String, params: [String: AnyObject], finished: XJNetFinishedCallBack) {
        
        // 1. 定义成功的闭包
        let successCallBack: (NSURLSessionDataTask!, AnyObject!) -> Void = { (_, JSON) -> Void in
            
            if let result = JSON as? [String: AnyObject] {
                // 有结果的回调
                finished(result: result, error: nil)
            } else {
                // 没有错误, 同时没有结果
                print("没有数据 \(method) Request \(urlString)")
                
                finished(result: nil, error: XJNetworkError.emptyDataError.error())
            }
        }
        
        // 2. 定义失败的闭包
        let failedCallBack: (NSURLSessionDataTask!, NSError!) -> Void = { (_, error) -> Void in
            print(error)
            
            finished(result: nil, error: error)
        }

        // 3. 根据method 来选择执行的方法
        switch method {
        case .GET:
            GET(urlString, parameters: params, success: successCallBack, failure: failedCallBack)
        case .POST:
            POST(urlString, parameters: params, success: successCallBack, failure: failedCallBack)
        }
    }
    
    ///  上传图像
    ///
    ///  - parameter urlString: urlString地址
    ///  - parameter image:     图像
    ///  - parameter params:    参数字典
    ///  - parameter finished:  完成回调
    private func uploadImage(urlString: String, image: UIImage, params: [String: AnyObject], finished: XJNetFinishedCallBack) {
        
        // 1. 定义成功的闭包
        let successCallBack: (NSURLSessionDataTask!, AnyObject!) -> Void = { (_, JSON) -> Void in
            
            if let result = JSON as? [String: AnyObject] {
                // 有结果的回调
                finished(result: result, error: nil)
            } else {
                // 没有错误, 同时没有结果
                print("没有数据 \(urlString)")
                
                finished(result: nil, error: XJNetworkError.emptyDataError.error())
            }
        }
        
        // 2. 定义失败的闭包
        let failedCallBack: (NSURLSessionDataTask!, NSError!) -> Void = { (_, error) -> Void in
            print(error)
            
            finished(result: nil, error: error)
        }
        
        POST(urlString, parameters: params, constructingBodyWithBlock: { (formData) -> Void in
            
            // 1. 将 image 转换成二进制数据
            let data = UIImagePNGRepresentation(image)!
            
            // 2. 构造数据体
            /**
                name: 服务器要求的字段名
                fileName: 保存在服务器的文件名 提示: 如果接口文档没有指定文件名参数, 可以随便写
                mimeType: 告诉服务器上传文件的类型, 如果不想告诉服务器, 可以使用 applicatioin/octet-stream
                    格式: 大类/小类
                    常见的格式:
                        image/png
                        image/jpg
                        image/gif
                        text/html
                        text/plain
            */
            formData.appendPartWithFileData(data, name: "pic", fileName: "xxx", mimeType: "applicatioin/octet-stream")
            }, success: successCallBack, failure: failedCallBack)
    }
}
